1 module hip.network;
2 import hip.console.log;
3 import hip.api.net.utils;
4 import hip.api.net.hipnet;
5 
6 struct NetBufferStream
7 {
8 	NetBuffer self;
9 
10 	size_t currentOffset;
11 
12 	/**
13 	 *
14 	 * Params:
15 	 *   data = The data to append to this stream. The buffer is sliced to remove the size took from it.
16 	 * Returns: If it has already finished appending (it is based on how much data it expectes)
17 	 */
18 	bool appendData(ref ubyte[] data)
19 	{
20 		if(isFinished())
21 			return true;
22 		if(expectedSize > buffer.length)
23 			buffer.length = expectedSize;
24 
25 		size_t remainingSize = expectedSize - currentOffset;
26 		size_t sizeToTake = remainingSize < data.length ? remainingSize : data.length;
27 
28 		buffer[currentOffset..currentOffset+sizeToTake] = data[0..sizeToTake];
29 		currentOffset+= sizeToTake;
30 		data = data[sizeToTake..$];
31 		return currentOffset == expectedSize;
32 	}
33 
34 
35 	bool isFinished() const { return currentOffset == expectedSize; }
36 
37 	///Resets it to a reusable state
38 	void reset()
39 	{
40 		currentOffset = 0;
41 		header = header.init;
42 	}
43 
44 	alias self this;
45 }
46 
47 
48 interface INetworkBackend
49 {
50 	/**
51 	 *
52 	 * Params:
53 	 *   ip = The IP to connect
54 	 *   onConnect = Used for sending messages on the connect. WARNING: If you're using NetController, do not call `connect` from that interface.
55 	 *   id = ID of the address to connect to. Relevant when you're not using a P2P connection. Enforced when using websockets, since direct connection is unavailable.
56 	 * Returns: The connection status
57 	 */
58 	NetConnectStatus connect(NetIPAddress ip, void delegate() onConnect, uint id = NetID.server);
59 	/**
60 	 * Tries to reconnect to the same address on initial connect.
61 	 */
62 	void attemptReconnection();
63 	///Gets ID for that network connection. It can't change over its lifetime
64 	uint getConnectionSelfID() const;
65 
66 	///Gets ID for the currently target network connection. Changed with the setter
67 	uint targetConnectionID() const;
68 	///Sets that network connection connected to the specified ID
69 	void targetConnectionID(uint id);
70 
71 	bool isHost() const;
72 
73 	///Sends the data by using a header
74 	bool sendData(ubyte[] data);
75 
76 	void disconnect();
77 
78 	size_t getData(ref ubyte[] tempBuffer);
79 	NetConnectStatus status() const;
80 }
81 
82 final class HipNetwork : INetwork
83 {
84 	INetworkBackend netInterface;
85 	NetInterface itf;
86 	NetBufferStream currentStream;
87 	NetBufferStream[] completedStreams;
88 	NetBufferStream[] streamPool;
89 
90 	private NetConnectInfo connInfo;
91 	private bool attemptedConnection;
92 	private void delegate() onConnect;
93 
94 	this(NetInterface itf)
95 	{
96 		import hip.net.backend.initializer;
97 		this.itf = itf;
98 		netInterface = getNetworkImplementation(itf);
99 	}
100 
101 	NetConnectStatus connect(NetIPAddress ip, void delegate(INetwork) onConnect, uint id = NetID.server)
102 	{
103 		if(!attemptedConnection)
104 			attemptedConnection = true;
105 		connInfo = NetConnectInfo(ip, id);
106 		this.onConnect = (){onConnect(this);};
107 
108 		return netInterface.connect(ip, this.onConnect, id);
109 	}
110 
111 	bool isHost() const { return netInterface.isHost; }
112 	uint getConnectionSelfID() const{return netInterface.getConnectionSelfID();}
113 	uint targetConnectionID() const {return netInterface.targetConnectionID();}
114 	void targetConnectionID(uint id){netInterface.targetConnectionID(id);}
115 
116 	private NetBufferStream getNetBufferStream(NetHeader header)
117 	{
118 		NetBufferStream ret;
119 		if(streamPool.length > 0)
120 		{
121 			ret = streamPool[0];
122 			streamPool = streamPool[1..$];
123 			ret.header = header;
124 		}
125 		else
126 			ret = NetBufferStream(NetBuffer(header));
127 		return ret;
128 	}
129 
130 	/**
131 	 * Only returns true if the buffer has been completely loaded by the size expected by the package.
132 	 *
133 	 * This function implements a data getter API which does not block execution (if the underlying implementation is non-blocking )
134 	 * Params:
135 	 *   data = The data if this functions returns true
136 	 * Returns: True when package is fully received. False if not connected or still pending data
137 	 */
138 	bool getData()
139 	{
140 		if(netInterface.status == NetConnectStatus.waiting)
141 			netInterface.connect(connInfo.ip, this.onConnect, connInfo.id);
142 		if(netInterface.status == NetConnectStatus.attemptingReconnect)
143 		{
144 			hiplog("Trying to reconnect.");
145 			netInterface.attemptReconnection();
146 		}
147 
148 		if(netInterface.status != NetConnectStatus.connected)
149 			return false;
150 		if(completedStreams.length != 0)
151 			return true;
152 
153 		ubyte[4096] tempBufferData = void;
154 		ubyte[] tempBuffer = tempBufferData;
155 
156 
157 		size_t dataLength = getDataBackend(tempBuffer);
158 		if(dataLength == 0)
159 			return false;
160 		if(dataLength < NetHeader.sizeof)
161 			throw new Exception("Some bug occurred: Data received is less than a NetHeader size.");
162 
163 		while(dataLength != 0)
164 		{
165 			if(currentStream.isInvalid)
166 			{
167 				currentStream = getNetBufferStream(*cast(NetHeader*)(tempBuffer.ptr));
168 				dataLength-= NetHeader.sizeof;
169 				tempBuffer = tempBuffer[NetHeader.sizeof..$];
170 			}
171 
172 			//If it didn't fill buffer, that means there is no data left hanging, return if we completed any stream
173 			if(!currentStream.appendData(tempBuffer))
174 				return completedStreams.length != 0;
175 
176 
177 			completedStreams~= currentStream;
178 			dataLength-= currentStream.expectedSize;
179 			currentStream = currentStream.init;
180 		}
181 
182 
183 		return completedStreams.length != 0;
184 	}
185 
186 	/**
187 	 * WARNING: It is important to call freeBuffer at some time since getting the buffer won't remove it from the queue
188 	 * Returns: A buffer from the completed list. You should free it at some time so its memory can be reused.
189 	 */
190 	NetBuffer* getCompletedBuffer() const
191 	{
192 		if(completedStreams.length == 0)
193 			throw new Exception("No data has been received yet. Wait for getData() to return true before calling that function.");
194 		return cast(NetBuffer*)&completedStreams[0];
195 	}
196 
197 	/**
198 	 * Make that buffer available inside the buffer pool and saves memory
199 	 * Params:
200 	 *   buffer = A buffer inside the completed list
201 	 */
202 	void freeBuffer(NetBuffer* b)
203 	{
204 		NetBufferStream* buffer = cast(NetBufferStream*)b;
205 		for(int i = 0 ; i < completedStreams.length; i++)
206 			if(buffer == &completedStreams[i])
207 			{
208 				buffer.reset();
209 				completedStreams = completedStreams[0..i] ~ completedStreams[i+1..$];
210 				streamPool~= *buffer;
211 				i--;
212 			}
213 	}
214 
215 	/**
216 	 * Gets the data that is in front of queue and converts the data to the expected one.
217 	 * The correct usage of this function is to use it after `getData()`.
218 	 * If it has no data, an exception will be thrown
219 	 * Params:
220 	 *   data = Data from getData
221 	 * Returns:
222 	 */
223 	T getDataAsType(T)()
224 	{
225 		NetBufferStream* stream = getCompletedBuffer();
226 		ubyte[] data = stream.getFinishedBuffer();
227 		scope(exit)
228 			freeBuffer(stream);
229 		return interpretNetworkData(stream.header, data);
230 	}
231 
232 	void sendDataRaw(ubyte[] data)
233 	{
234 		netInterface.sendData(data);
235 	}
236 	private size_t getDataBackend(ref ubyte[] data)
237 	{
238 		size_t ret = netInterface.getData(data);
239 		if(ret != 0)
240 		{
241 			data = data[0..ret];
242 			toNetworkBytesInPlace(data);
243 		}
244 		return ret;
245 	}
246 
247 	void disconnect()
248 	{
249 		if(status == NetConnectStatus.connected)
250 		{
251 			send_disconnect(this);
252 			netInterface.disconnect();
253 		}
254 	}
255 	NetConnectStatus status() const { return netInterface.status; }
256 	NetInterface getNetInterfaceType() const { return itf; }
257 	NetConnectInfo getConnectInfo() const { return connInfo; }
258 
259 }
260 
261 private __gshared INetwork[] netConnections;
262 
263 export extern(System) INetwork getNetworkInterface(NetInterface itf = NetInterface.automatic)
264 {
265 	netConnections~= new HipNetwork(itf);
266 	return netConnections[$-1];
267 }
268 
269 
270 /**
271  * Disconnects every net connection that was loaded at the beginning.
272  */
273 void disconnectNetwork()
274 {
275 	foreach(conn; netConnections)
276 		conn.disconnect();
277 	netConnections = null;
278 }